 /**
 * 高斯消元法解线性方程组:
 *      1.数量必须对应（参数方阵外加一列结果值，如：
 *                     [a11,a12,a13,a14,val],
 *                     [a21,a22,a23,a24,val],
 *                     [a31,a32,a33,a34,val],
 *                     [a41,a42,a43,a44,val],
 *        ）
 *      2.列主选择排序规则（绝对值最大），列主选择失败时用MIN代替0，
 *        结果可能不符合预期
 *      3.奇异矩阵返回false（rank为秩）,求秩用MIN值，
 *        因为理论上的非奇异矩阵在计算机精度下仍然有可能是欠秩的，
 *        负面效应待查
 * 19.2.14:更新列主排序函数，排除零值
 * 19.12.12：规范化后使用quickSort排序，未测试
 * * */

import Utils from '../general/utils';
let matN={};
let solution=[];
let MIN=1e-10;
let enforce=true;

export default class GaussLinearSys{
    constructor(){
        console.warn('do not instantiate class GaussLinearSys,use the static methods provided!');
    }

    /**
     * 是否强制求解（欠秩时将以MIN值代替计算）
     * @param bool
     */
    static set forceSolution(bool){
        enforce=bool;
    }

    /**
     * 设置替代值MIN
     * @param val
     */
    static set minValue(val){
        MIN=val;
    }

    /**
     * 获取线性方程组的秩
     * @returns {number}
     */
    static get rank(){
        GaussLinearSys.triangulate();
        let rk=0;
        for(let key in matN){
            let eq=matN[''+key+''];
            let ks=Object.keys(eq);
            let isOk=false;
            for(let i=0;i<ks.length-1;i++){
                let k=ks[i];
                let tn=eq[''+k+''];
                if((!Number.isNaN(tn))&&
                    (tn!==Number.NEGATIVE_INFINITY)&&
                    (tn!==Number.POSITIVE_INFINITY)&&
                    (Math.abs(tn)>MIN)
                ){
                    isOk=true;
                    break;
                }
            }
            if(isOk)rk++;
        }
        return rk;
    }

    /**
     * 获取方程组的解
     * @returns {boolean|[]}
     */
    static get solution(){
        let rk=GaussLinearSys.rank;
        if(rk<Object.keys(matN).length)return false;
        GaussLinearSys.solute();
        return solution;
    }

    /**
     * 添加方程组
     * @param paras_result_array
     * @returns {boolean}
     */
    static add(...paras_result_array) {
        solution = [];
        matN = {};
        if (paras_result_array.length === 0 || paras_result_array[0].length < 2 || paras_result_array.length < paras_result_array[0].length - 1) {
            console.log('invalid equation group!');
            return false;
        }
        let ordered = GaussLinearSys.columnOrder(paras_result_array);
        if (!ordered) return false;
        for (let r = 0; r < ordered.length; r++) {
            solution.push(0);
            let pra = ordered[r];
            matN['R' + r + ''] = {};
            for (let c = 0; c < pra.length; c++) {
                if (c === pra.length - 1) {
                    matN['R' + r + '']['val'] = pra[c];
                } else {
                    matN['R' + r + '']['C' + c + ''] = pra[c];
                }
            }
        }
        return true;
    }

    /**
     * 列主排序
     * @param _array
     * @returns {null|[]}
     */
    static columnOrder(_array){
        let array=Utils.copy(_array);
        let count = array[0].length;
        let result=[];

        let getColInfo=()=> {
            let tp=[];
            for (let i = 0; i < count - 1; i++) {
                let ia = [];
                let va = [];
                for (let j = 0; j < array.length; j++) {
                    let ary = array[j];
                    if (ary[i] === 0)continue;
                    ia.push(j);
                    va.push(Math.abs(ary[i]));
                }

                if(ia.length>0){
                    Utils.quickSort(ia,(a, b)=>{
                        return va[a]>va[b];
                    });
                    tp.push(ia);
                }
            }
            return tp;
        };
        let colInfo=getColInfo();
        if(colInfo.length<count-1){
            if(!enforce)return null;
            console.warn('ordination failed,will use minimal value!');
            for(let i=0;i<array.length;i++){
                for(let j=0;j<array[i].length;j++){
                    array[i][j]=array[i][j]===0?MIN:array[i][j];
                }
            }
        }
        colInfo=getColInfo();

        let choose=(ctn=[],step=-1,id)=>{
            if(ctn.findIndex((i)=>i===id)!==-1)return false;
            if(id!==null)ctn.push(id);
            step++;
            let col=colInfo[step];
            if(col) {
                let ch=null;
                for (let idx of col) {
                    ch = choose([...ctn], step, idx);
                    if (ch)break;
                }
                if(!ch)return false;
                return ch;
            }
            return ctn;
        };
        let rId=choose([],-1,null);

        if(rId.length<count-1){
            if(!enforce)return null;
        }

        if(rId.length<count-1){
            console.warn('failed to sort by Maximal Column Pivoting,will go through with a minimal value!');
            for(let i=0;i<array.length;i++){
                result[i]=[];
                for(let j=0;j<array[i].length;j++){
                    result[i][j]=array[i][j]===0?MIN:array[i][j];
                }
            }
        }else {
            for (let i of rId) {
                result.push(array[i]);
            }
        }

        return result;
    }

    /**
     * 三角化
     */
    static triangulate(){
        let cR,mR,k;
        for(let i=0;i<Object.keys(matN).length-1;i++){
            cR=matN[''+Object.keys(matN)[i]+''];
            for(let j=i+1;j<Object.keys(matN).length;j++){
                mR=matN[''+Object.keys(matN)[j]+''];
                k=mR['C'+i+'']/cR['C'+i+''];
                mR[''+Object.keys(mR)[i]+'']=0;
                for(let h= i+1;h<Object.keys(mR).length;h++){
                    mR[''+Object.keys(mR)[h]+'']-=cR[''+Object.keys(cR)[h]+'']*k;
                }
            }
        }
    }

    /**
     * 方程组求解
     */
    static solute(){
        let cR,le;
        for(let r=Object.keys(matN).length-1;r>=0;r--){
            cR=matN[''+Object.keys(matN)[r]+''];
            le=cR['val'];
            for(let c=r+1;c<Object.keys(cR).length-1;c++){
                le-=cR[''+Object.keys(cR)[c]+'']*solution[c];
            }
            solution[r]=le/cR[''+Object.keys(cR)[r]+''];
        }
    }
}